/**
 * Created by enroll on 2018/6/24.
 */

import { HttpService, HTTPMethods } from "http-service";
import { Injectable } from "@angular/core";
import { ResponseData } from "../../models/response-data";
import { Observable } from "rxjs/Observable";
import { JSONMapping } from "../../models/json-mapping";
import { KeyValuePairs } from "http-service/src/request";
import { ObjectDto } from "../sqliteDB/object.dto";
import { SqliteService } from "../sqliteDB/sqlite.service";
import { JsonMappingUtil } from "../../models/json-mapping-util";
import {SyncStatus} from "../../models/sync-status";
import {Attachment} from "../../models/transform/attachment";
import {AttachmentDto} from "../../models/dto/attachment.dto";
import {SecureStorageService} from "../secure-storage.services";
import {UtilsService} from "../utils.services";
import {StorageService} from "../../models/services/storage.service";
import {LastFetchingDate} from "../../models/lastFetchingDate";
import {CustomStorage} from "../../models/transform/custom-storage";
import {StorageDto} from "../../models/dto/storage.dto";
import {CurdService} from "../../models/services/curd.service";


@Injectable()
export class SyncUtilService {


  customStorageData: Array<object>;

  constructor(private httpService: HttpService,
              private sqliteService: SqliteService,
              private storage: SecureStorageService,
              private curdService: CurdService,
              private customeStorageService: StorageService,
              private utilsService: UtilsService) {



  }

  // transform Mapping to Dto
  toObjectDto<T extends ObjectDto, M extends JSONMapping>(from: M, to: { new(): T }): T {
    let dto = new to();
    JsonMappingUtil.MappingAllProp(from, dto);
    return dto;
  }

  toObjectFromDto<T extends JSONMapping, DTO extends ObjectDto>(from: DTO, to: { new(obj: object): T }): T {
    return JsonMappingUtil.MappingAllProp(from, new to({}));
  }

  fetchAllData(lastFetchingDate: Date, api): Observable<any> {
    if (lastFetchingDate) {
      return this.httpService.fetchWithPath(api, { lastFetchingDate: lastFetchingDate.getTime() });
    }
    else {
      return this.httpService.fetchWithPath(api);
    }
  }

  fetchPropertyWithArray<T extends ObjectDto>(dtoConstructorFunc: { new(): T }, propertyName: string): Observable<Array<string>> {
    let propertyNameArray = propertyName.split(",");
    return this.fetchPropertiesWithArray(dtoConstructorFunc, propertyNameArray)
      .map((objs: Array<object>) => objs.map((obj) => {
        let propertyValue: string = "";
        propertyNameArray.forEach((propertyNameItem) => {
          if (obj[propertyNameItem]) {
            propertyValue += obj[propertyNameItem];
          }
        });
        return propertyValue;
      }));
  }

  fetchPropertiesWithArray<T extends ObjectDto>(dtoConstructorFunc: { new(): T }, propertyKeys: Array<string>): Observable<Array<object>> {
    return Observable.fromPromise(this.sqliteService.simpleFetch(dtoConstructorFunc, propertyKeys))
      .map((rows: { results: Array<object> }) => {
        return Array.from(rows.results);
      });
  }

  // 返回transform Mapping后对象
  remoteFetchWithArray<T extends JSONMapping>(
    lastFetchingDate: Date, api,
    modelContructorFunc: { new(json: KeyValuePairs): T },
    page = null, pageSize = null
  ): Observable<Array<T>> {
    let ob: Observable<ResponseData> = null;
    let params = {};
    if (lastFetchingDate) {
      params = {lastFetchingDate: lastFetchingDate.getTime()};
    }
    if (page) {
      params = {...params, page: page, pageSize: pageSize};
    }
    console.log(api, params);
    ob = this.httpService.fetchWithPath(api, params);

    return ob.map((data: ResponseData) => {
      if (data && data.data && Array.isArray(data.data)) {
        let jsonObjects = data.data;
        // console.log(jsonObjects);
        return jsonObjects.map( (json: KeyValuePairs) => new modelContructorFunc(json));
      }
      return [];
    })
      .catch((error) => {
        return Observable.of(error);
      });
  }

  public downImageAndGetLocalUrl(attachments: Array<Attachment>) {
    return attachments.map((attachment) => {
      return this.storage.get('access_token')
        .flatMap((token) => {
          let id = attachment.id;
          if (attachment.thumbnailId) {
            id = attachment.thumbnailId;
          }
          return this.utilsService.downImage(id, token)
            .map((res) => {
              if (typeof(res) === "string") {
                return [];
              }
              attachment.path = res["nativeURL"];
              return attachment;
            });
        });
    });
  }

  public saveAttachment(attachments: Array<Attachment>): Observable<boolean> {
    let attachmentDtos = attachments.map((attachment) => {
      return this.toObjectDto(attachment, AttachmentDto);
    });
    if (attachmentDtos.length > 0) {
      return this.saveData(attachmentDtos, AttachmentDto, "id");
    } else {
      return Observable.of(true);
    }
  }

  public getApiLastFetchingDate(key): Observable<Date> {
    return this.customeStorageService.fetchValueByKey(key)
      .map((val) => {
        let lastFetchingDate: Date = null;
        if (val && val.length > 0) {
          lastFetchingDate = new Date(parseInt(val, 10));
        }
        return lastFetchingDate;
      });
  }

  public getApiLastFetchingPage(key): Observable<number> {
    return this.customeStorageService.fetchValueByKey(key)
      .flatMap((val) => {
        let lastFetchingPage: number = 1;
        if (val && val.length > 0) {
          lastFetchingPage = parseInt(val, 0);
          if (lastFetchingPage === 1) {
            return Observable.of(lastFetchingPage);
          } else {
            return Observable.of(lastFetchingPage + 1);
          }

        } else {
          let lastFetchingDateStorage = new CustomStorage({
            key: key,
            value: "1"
          });
          return this.saveData(
            [this.toObjectDto(lastFetchingDateStorage, StorageDto)],
            StorageDto,
            "key")
            .map(() => lastFetchingPage);
        }
      });
  }

  public saveApiLastFetchingDate(row: {key: string, value: string}) {
    let lastFetchingDateStorage = new CustomStorage(row);
    this.saveData(
      [this.toObjectDto(lastFetchingDateStorage, StorageDto)],
      StorageDto,
      "key").subscribe();
  }

  updateStoragePage(storageData: {
    key: string, value: string
  }) {
    return Observable.fromPromise(this.curdService.update({
      dto: StorageDto, mapObj: CustomStorage
    }, storageData, {
      and: {
        key: storageData.key
      }
    }));
  }

  saveData<T extends ObjectDto>(
    dtos: Array<T>,
    dtoConstructorFunc: { new(): T },
    existCheckingPropertyKey?: string, onlyInsert: boolean = false) {

    let ob: Observable<Array<string>> = Observable.of([]);
    if (existCheckingPropertyKey) {
      // get unique property value
      ob = this.fetchPropertyWithArray(dtoConstructorFunc, existCheckingPropertyKey);
    }
    console.log("------------save data-------()-------" + existCheckingPropertyKey);
    return ob.flatMap((existingPropertys: Array<string>) => {
      let updatedDtos = [];
      let newlyCreatedDtos = dtos;
      let dtoExistCheckingPropertyValue: string = "";
      let existCheckingPropertyKeyArray = [];
      if (existingPropertys && existingPropertys.length > 0) {
        newlyCreatedDtos = dtos.filter((dto: T) => {
          existCheckingPropertyKeyArray = existCheckingPropertyKey.split(",");
          existCheckingPropertyKeyArray.forEach((existCheckingPropertyKeyItem) => {
            dtoExistCheckingPropertyValue += dto[existCheckingPropertyKeyItem];
          });
          let exist = existingPropertys.indexOf(dtoExistCheckingPropertyValue) >= 0;
          if (exist) {
            updatedDtos.push(dto);
          }
          dtoExistCheckingPropertyValue = "";
          return !exist;
        });
      }
      if (newlyCreatedDtos && newlyCreatedDtos.length > 0) {
        newlyCreatedDtos.map((dto: T) => {
          dto.syncStatus = SyncStatus.Success;
        });
      }
      if (updatedDtos && updatedDtos.length > 0) {
        updatedDtos.map((dto: T) => {
          dto.syncStatus = SyncStatus.Success;
        });
      }
//      console.log(newlyCreatedDtos, "++++++++++++++++++++++++++++");
      let updatingOb: Observable<boolean> = Observable.of(true);
      let creatingOb: Observable<boolean> = Observable.of(true);
      if (!onlyInsert && updatedDtos.length > 0) {
        updatingOb = this.sqliteService.updateRows(updatedDtos, existCheckingPropertyKeyArray);
      }
      if (newlyCreatedDtos.length > 0) {
        creatingOb = this.sqliteService.addRows(newlyCreatedDtos);
      }
      return updatingOb.flatMap(() => creatingOb);
    });
  }


}
